/*
 * Copyright (c) 2016-2017, ARM Limited and Contributors. All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <arch.h>
#include <arch_helpers.h>
#include <assert.h>
#include <debug.h>
#include <delay_timer.h>
#include <dw_mmc.h>
#include <emmc.h>
#include <errno.h>
#include <mmio.h>
#include <string.h>

#define DWMMC_CTRL            (0x00)
#define CTRL_IDMAC_EN            (1 << 25)
#define CTRL_DMA_EN            (1 << 5)
#define CTRL_INT_EN            (1 << 4)
#define CTRL_DMA_RESET            (1 << 2)
#define CTRL_FIFO_RESET            (1 << 1)
#define CTRL_RESET            (1 << 0)
#define CTRL_RESET_ALL            (CTRL_DMA_RESET | CTRL_FIFO_RESET | \
                     CTRL_RESET)

#define DWMMC_PWREN            (0x04)
#define DWMMC_CLKDIV            (0x08)
#define DWMMC_CLKSRC            (0x0c)
#define DWMMC_CLKENA            (0x10)
#define DWMMC_TMOUT            (0x14)
#define DWMMC_CTYPE            (0x18)
#define CTYPE_8BIT            (1 << 16)
#define CTYPE_4BIT            (1)
#define CTYPE_1BIT            (0)

#define DWMMC_BLKSIZ            (0x1c)
#define DWMMC_BYTCNT            (0x20)
#define DWMMC_INTMASK            (0x24)
#define INT_EBE                (1 << 15)
#define INT_SBE                (1 << 13)
#define INT_HLE                (1 << 12)
#define INT_FRUN            (1 << 11)
#define INT_DRT                (1 << 9)
#define INT_RTO                (1 << 8)
#define INT_DCRC            (1 << 7)
#define INT_RCRC            (1 << 6)
#define INT_RXDR            (1 << 5)
#define INT_TXDR            (1 << 4)
#define INT_DTO                (1 << 3)
#define INT_CMD_DONE            (1 << 2)
#define INT_RE                (1 << 1)

#define DWMMC_CMDARG            (0x28)
#define DWMMC_CMD            (0x2c)
#define CMD_START            (1 << 31)
#define CMD_USE_HOLD_REG        (1 << 29)    /* 0 if SDR50/100 */
#define CMD_UPDATE_CLK_ONLY        (1 << 21)
#define CMD_SEND_INIT            (1 << 15)
#define CMD_STOP_ABORT_CMD        (1 << 14)
#define CMD_WAIT_PRVDATA_COMPLETE    (1 << 13)
#define CMD_WRITE            (1 << 10)
#define CMD_DATA_TRANS_EXPECT        (1 << 9)
#define CMD_CHECK_RESP_CRC        (1 << 8)
#define CMD_RESP_LEN            (1 << 7)
#define CMD_RESP_EXPECT            (1 << 6)
#define CMD(x)                (x & 0x3f)

#define DWMMC_RESP0            (0x30)
#define DWMMC_RESP1            (0x34)
#define DWMMC_RESP2            (0x38)
#define DWMMC_RESP3            (0x3c)
#define DWMMC_RINTSTS            (0x44)
#define DWMMC_STATUS            (0x48)
#define STATUS_DATA_BUSY        (1 << 9)

#define DWMMC_FIFOTH            (0x4c)
#define FIFOTH_TWMARK(x)        (x & 0xfff)
#define FIFOTH_RWMARK(x)        ((x & 0x1ff) << 16)
#define FIFOTH_DMA_BURST_SIZE(x)    ((x & 0x7) << 28)

#define DWMMC_DEBNCE            (0x64)
#define DWMMC_BMOD            (0x80)
#define BMOD_ENABLE            (1 << 7)
#define BMOD_FB                (1 << 1)
#define BMOD_SWRESET            (1 << 0)

#define DWMMC_DBADDR            (0x88)
#define DWMMC_IDSTS            (0x8c)
#define DWMMC_IDINTEN            (0x90)
#define DWMMC_CARDTHRCTL        (0x100)
#define CARDTHRCTL_RD_THR(x)        ((x & 0xfff) << 16)
#define CARDTHRCTL_RD_THR_EN        (1 << 0)

#define IDMAC_DES0_DIC            (1 << 1)
#define IDMAC_DES0_LD            (1 << 2)
#define IDMAC_DES0_FS            (1 << 3)
#define IDMAC_DES0_CH            (1 << 4)
#define IDMAC_DES0_ER            (1 << 5)
#define IDMAC_DES0_CES            (1 << 30)
#define IDMAC_DES0_OWN            (1 << 31)
#define IDMAC_DES1_BS1(x)        ((x) & 0x1fff)
#define IDMAC_DES2_BS2(x)        (((x) & 0x1fff) << 13)

#define DWMMC_DMA_MAX_BUFFER_SIZE    (512 * 8)

#define DWMMC_8BIT_MODE            (1 << 6)

#define TIMEOUT                100000

struct dw_idmac_desc {
    unsigned int    des0;
    unsigned int    des1;
    unsigned int    des2;
    unsigned int    des3;
};

static void dw_init(void);
static int dw_send_cmd(emmc_cmd_t *cmd);
static int dw_set_ios(int clk, int width);
static int dw_prepare(int lba, uintptr_t buf, size_t size);
static int dw_read(int lba, uintptr_t buf, size_t size);
static int dw_write(int lba, uintptr_t buf, size_t size);

static const emmc_ops_t dw_mmc_ops = {
    .init        = dw_init,
    .send_cmd    = dw_send_cmd,
    .set_ios    = dw_set_ios,
    .prepare    = dw_prepare,
    .read        = dw_read,
    .write        = dw_write,
};

static dw_mmc_params_t dw_params;

static void dw_update_clk(void)
{
    unsigned int data;

    mmio_write_32(dw_params.reg_base + DWMMC_CMD,
              CMD_WAIT_PRVDATA_COMPLETE | CMD_UPDATE_CLK_ONLY |
              CMD_START);
    while (1) {
        data = mmio_read_32(dw_params.reg_base + DWMMC_CMD);
        if ((data & CMD_START) == 0)
            break;
        data = mmio_read_32(dw_params.reg_base + DWMMC_RINTSTS);
        assert(data & INT_HLE);
    }
}

static void dw_set_clk(int clk)
{
    unsigned int data;
    int div;

    assert(clk > 0);

    for (div = 1; div < 256; div++) {
        if ((dw_params.clk_rate / (2 * div)) <= clk) {
            break;
        }
    }
    assert(div < 256);

    /* wait until controller is idle */
    do {
        data = mmio_read_32(dw_params.reg_base + DWMMC_STATUS);
    } while (data & STATUS_DATA_BUSY);

    /* disable clock before change clock rate */
    mmio_write_32(dw_params.reg_base + DWMMC_CLKENA, 0);
    dw_update_clk();

    mmio_write_32(dw_params.reg_base + DWMMC_CLKDIV, div);
    dw_update_clk();

    /* enable clock */
    mmio_write_32(dw_params.reg_base + DWMMC_CLKENA, 1);
    mmio_write_32(dw_params.reg_base + DWMMC_CLKSRC, 0);
    dw_update_clk();
}

static void dw_init(void)
{
    unsigned int data;
    uintptr_t base;

    assert((dw_params.reg_base & EMMC_BLOCK_MASK) == 0);

    base = dw_params.reg_base;
    mmio_write_32(base + DWMMC_PWREN, 1);
    mmio_write_32(base + DWMMC_CTRL, CTRL_RESET_ALL);
    do {
        data = mmio_read_32(base + DWMMC_CTRL);
    } while (data);

    /* enable DMA in CTRL */
    data = CTRL_INT_EN | CTRL_DMA_EN | CTRL_IDMAC_EN;
    mmio_write_32(base + DWMMC_CTRL, data);
    mmio_write_32(base + DWMMC_RINTSTS, ~0);
    mmio_write_32(base + DWMMC_INTMASK, 0);
    mmio_write_32(base + DWMMC_TMOUT, ~0);
    mmio_write_32(base + DWMMC_IDINTEN, ~0);
    mmio_write_32(base + DWMMC_BLKSIZ, EMMC_BLOCK_SIZE);
    mmio_write_32(base + DWMMC_BYTCNT, 256 * 1024);
    mmio_write_32(base + DWMMC_DEBNCE, 0x00ffffff);
    mmio_write_32(base + DWMMC_BMOD, BMOD_SWRESET);
    do {
        data = mmio_read_32(base + DWMMC_BMOD);
    } while (data & BMOD_SWRESET);
    /* enable DMA in BMOD */
    data |= BMOD_ENABLE | BMOD_FB;
    mmio_write_32(base + DWMMC_BMOD, data);

    udelay(100);
    dw_set_clk(EMMC_BOOT_CLK_RATE);
    udelay(100);
}

static int dw_send_cmd(emmc_cmd_t *cmd)
{
    unsigned int op, data, err_mask;
    uintptr_t base;
    int timeout;

    assert(cmd);

    base = dw_params.reg_base;

    switch (cmd->cmd_idx) {
    case EMMC_CMD0:
        op = CMD_SEND_INIT;
        break;
    case EMMC_CMD12:
        op = CMD_STOP_ABORT_CMD;
        break;
    case EMMC_CMD13:
        op = CMD_WAIT_PRVDATA_COMPLETE;
        break;
    case EMMC_CMD8:
    case EMMC_CMD17:
    case EMMC_CMD18:
        op = CMD_DATA_TRANS_EXPECT | CMD_WAIT_PRVDATA_COMPLETE;
        break;
    case EMMC_CMD24:
    case EMMC_CMD25:
        op = CMD_WRITE | CMD_DATA_TRANS_EXPECT |
             CMD_WAIT_PRVDATA_COMPLETE;
        break;
    default:
        op = 0;
        break;
    }
    op |= CMD_USE_HOLD_REG | CMD_START;
    switch (cmd->resp_type) {
    case 0:
        break;
    case EMMC_RESPONSE_R2:
        op |= CMD_RESP_EXPECT | CMD_CHECK_RESP_CRC |
              CMD_RESP_LEN;
        break;
    case EMMC_RESPONSE_R3:
        op |= CMD_RESP_EXPECT;
        break;
    default:
        op |= CMD_RESP_EXPECT | CMD_CHECK_RESP_CRC;
        break;
    }
    timeout = TIMEOUT;
    do {
        data = mmio_read_32(base + DWMMC_STATUS);
        if (--timeout <= 0)
            panic();
    } while (data & STATUS_DATA_BUSY);

    mmio_write_32(base + DWMMC_RINTSTS, ~0);
    mmio_write_32(base + DWMMC_CMDARG, cmd->cmd_arg);
    mmio_write_32(base + DWMMC_CMD, op | cmd->cmd_idx);

    err_mask = INT_EBE | INT_HLE | INT_RTO | INT_RCRC | INT_RE |
           INT_DCRC | INT_DRT | INT_SBE;
    timeout = TIMEOUT;
    do {
        udelay(500);
        data = mmio_read_32(base + DWMMC_RINTSTS);

        if (data & err_mask)
            return -EIO;
        if (data & INT_DTO)
            break;
        if (--timeout == 0) {
            ERROR("%s, RINTSTS:0x%x\n", __func__, data);
            panic();
        }
    } while (!(data & INT_CMD_DONE));

    if (op & CMD_RESP_EXPECT) {
        cmd->resp_data[0] = mmio_read_32(base + DWMMC_RESP0);
        if (op & CMD_RESP_LEN) {
            cmd->resp_data[1] = mmio_read_32(base + DWMMC_RESP1);
            cmd->resp_data[2] = mmio_read_32(base + DWMMC_RESP2);
            cmd->resp_data[3] = mmio_read_32(base + DWMMC_RESP3);
        }
    }
    return 0;
}

static int dw_set_ios(int clk, int width)
{
    switch (width) {
    case EMMC_BUS_WIDTH_1:
        mmio_write_32(dw_params.reg_base + DWMMC_CTYPE, CTYPE_1BIT);
        break;
    case EMMC_BUS_WIDTH_4:
        mmio_write_32(dw_params.reg_base + DWMMC_CTYPE, CTYPE_4BIT);
        break;
    case EMMC_BUS_WIDTH_8:
        mmio_write_32(dw_params.reg_base + DWMMC_CTYPE, CTYPE_8BIT);
        break;
    default:
        assert(0);
    }
    dw_set_clk(clk);
    return 0;
}

static int dw_prepare(int lba, uintptr_t buf, size_t size)
{
    struct dw_idmac_desc *desc;
    int desc_cnt, i, last;
    uintptr_t base;

    assert(((buf & EMMC_BLOCK_MASK) == 0) &&
           ((size % EMMC_BLOCK_SIZE) == 0) &&
           (dw_params.desc_size > 0) &&
           ((dw_params.reg_base & EMMC_BLOCK_MASK) == 0) &&
           ((dw_params.desc_base & EMMC_BLOCK_MASK) == 0) &&
           ((dw_params.desc_size & EMMC_BLOCK_MASK) == 0));

    desc_cnt = (size + DWMMC_DMA_MAX_BUFFER_SIZE - 1) /
           DWMMC_DMA_MAX_BUFFER_SIZE;
    assert(desc_cnt * sizeof(struct dw_idmac_desc) < dw_params.desc_size);

    base = dw_params.reg_base;
    desc = (struct dw_idmac_desc *)dw_params.desc_base;
    mmio_write_32(base + DWMMC_BYTCNT, size);
    mmio_write_32(base + DWMMC_RINTSTS, ~0);
    for (i = 0; i < desc_cnt; i++) {
        desc[i].des0 = IDMAC_DES0_OWN | IDMAC_DES0_CH | IDMAC_DES0_DIC;
        desc[i].des1 = IDMAC_DES1_BS1(DWMMC_DMA_MAX_BUFFER_SIZE);
        desc[i].des2 = buf + DWMMC_DMA_MAX_BUFFER_SIZE * i;
        desc[i].des3 = dw_params.desc_base +
                   (sizeof(struct dw_idmac_desc)) * (i + 1);
    }
    /* first descriptor */
    desc->des0 |= IDMAC_DES0_FS;
    /* last descriptor */
    last = desc_cnt - 1;
    (desc + last)->des0 |= IDMAC_DES0_LD;
    (desc + last)->des0 &= ~(IDMAC_DES0_DIC | IDMAC_DES0_CH);
    (desc + last)->des1 = IDMAC_DES1_BS1(size - (last *
                  DWMMC_DMA_MAX_BUFFER_SIZE));
    /* set next descriptor address as 0 */
    (desc + last)->des3 = 0;

    mmio_write_32(base + DWMMC_DBADDR, dw_params.desc_base);
    clean_dcache_range(dw_params.desc_base,
               desc_cnt * DWMMC_DMA_MAX_BUFFER_SIZE);

    return 0;
}

static int dw_read(int lba, uintptr_t buf, size_t size)
{
    return 0;
}

static int dw_write(int lba, uintptr_t buf, size_t size)
{
    return 0;
}

void dw_mmc_init(dw_mmc_params_t *params)
{
    assert((params != 0) &&
           ((params->reg_base & EMMC_BLOCK_MASK) == 0) &&
           ((params->desc_base & EMMC_BLOCK_MASK) == 0) &&
           ((params->desc_size & EMMC_BLOCK_MASK) == 0) &&
           (params->desc_size > 0) &&
           (params->clk_rate > 0) &&
           ((params->bus_width == EMMC_BUS_WIDTH_1) ||
        (params->bus_width == EMMC_BUS_WIDTH_4) ||
        (params->bus_width == EMMC_BUS_WIDTH_8)));

    memcpy(&dw_params, params, sizeof(dw_mmc_params_t));
    emmc_init(&dw_mmc_ops, params->clk_rate, params->bus_width,
          params->flags);
}
